#include "access/BufferManager.h"
#include "storage/Page.h"

#include <ctime>
#include <iostream>


using namespace std;


BufferManager::BufferManager(int maxSize, int pSize, char* diskFileName) {
	
	this.maxSize = maxSize;
	
	entryArray = new BufTabEntry[this.maxSize];
	freeSlot = new queue<int>;
	table = new map<int, int>;
	
	for(int i = 0; i < maxSize; ++i) {
		entryArray[i].nodeID = -1;
		freeSlot.push(i);
	}
	
	cout << "Buffer manager created with : " << maxSize << " slots." << endl;

	ioTime = 0;
	updateTime = 0;
	this.pageSize = pSize * 1024;
	
	fileLock = PTHREAD_MUTEX_INITIALIZER;
	diskfile.open(diskFileName, ios::out);
	diskfile.close();
	diskfile.open("disk.dat", ios::in | ios::out | ios::binary);
}

BufferManager::~BufferManager() {
	for(int i = 0; i < maxSize; ++i) {
		if( entryArray[i].nodeID != -1 ) {
			delete (Page*)(entryArray[i].pointer);
		}
	}
	delete [] entryArray;
	diskfile.close();
}


bool BufferManager::readFromDisk(int id, char* buffer) {
	int pos = id * pageSize;
	pthread_mutex_lock(&fileLock);
	diskfile.seekg(pos, ios::beg);
	diskfile.read(buffer, pageSize);
	pthread_mutex_unlock(&fileLock);
	return true;
}

bool BufferManager::saveToDisk(int id, char* buffer) {
	int pos = id * pageSize;
	pthread_mutex_lock(&fileLock);
	diskfile.seekp(pos, ios::beg);
	diskfile.write(buffer, pageSize);
	pthread_mutex_unlock(&fileLock);
	return true;
}

bool BufferManager::UpdateEntry(void* pointer, int id) {
	if(id < 0 || id >= maxSize){
		cout << "Update Entry : Incorrect ID " <<  id << endl;
		return false;
    }    

	entryArray[id].pointer = pointer;

    return true;
}

bool BufferManager::AddEntry(void* pointer, int id) {
	if(id < 0 || id >= maxSize){
		cout << "Add Entry : Incorrect ID " <<  id << endl;
		return false;
	}

    //cout << "Add entry " << id << " " << pointer << endl;
    
	entryArray[id].pointer = pointer;
	entryArray[id].nodeID = id;
	entryArray[id].memBit = true;
	entryArray[id].dirtyBit = true;
	entryArray[id].last_time = updateTime++;

	openPageNum++;

	if(openPageNum > pageConstraint)
	{
		int saveEntry = PickupSaveEntry(id);
        //cout << entryArray[saveEntry].nodeID << " " 
        //    << (entryArray[saveEntry].pointer) << " " 
        //    << entryArray[saveEntry].memBit <<  endl;
		if(!((Page*)(entryArray[saveEntry].pointer))->SaveToFile())
		{
			cout << "save error" << endl;
		}
	}

	return true;
}

bool BufferManager::RemoveEntry(int id)
{
    // if invalid id
    if(id < 0 || id >= maxSize)
        return false;
        
    // if this page has not registered
    if(entryArray[id].nodeID == -1)
        return false;
    
    if(entryArray[id].memBit)
        openPageNum--;

    //cout << "Remove entry " << id << " " << entryArray[id].pointer << endl;

    entryArray[id].nodeID = -1;    
    
    return true;
}

// tell buffer manager the recent update for the page

void BufferManager::LoadUpdate(int id)
{
	if(id >= maxSize)
	{
		cout << "Load : Incorrect ID " << id << endl;
		return;
	}

	entryArray[id].last_time = updateTime++;
	entryArray[id].memBit = true;
	entryArray[id].dirtyBit = false;

	ioTime++;

	openPageNum++;

	if(openPageNum > pageConstraint)
	{
		int saveEntry = PickupSaveEntry(id);
		
		if(!((Page*)(entryArray[saveEntry].pointer))->SaveToFile())
		{

			cout << "file saving error" << endl;
		}

	}
}

// tell buffer manager we just save the page to disk 
void BufferManager::SaveUpdate(int id)
{
	if(id < 0 || id >= maxSize)
	{
		cout << "Save : Incorrect ID " << id << endl;
		return;
	}

	entryArray[id].memBit = false;

	openPageNum--;

	ioTime++;

}

// tell buffer manager 
void BufferManager::WriteUpdate(int id)
{
	if(id < 0 || id >= maxSize)
	{
		cout << "Write : Incorrect ID " << id << endl;
		return;
	}

	entryArray[id].last_time = updateTime++;
	//entryArray[id].memBit = true;
	entryArray[id].dirtyBit = true;
}

void BufferManager::ReadUpdate(int id)
{
	if(id >= maxSize)
	{
		cout << "Read : Incorrect ID " << id << endl;
		return;
	}

	entryArray[id].last_time = updateTime++;
	//entryArray[id].memBit = true;
}

int BufferManager::PickupSaveEntry(int curID)
{
	//int minCleanEntry = -1;
	int minEntry = -1;
	//time_t earlyCleanTime;
	time_t earlyTime;

	int i;

	for(i = 0; i < maxSize; i++)
	{
		if(i == curID)
			continue;

		if(entryArray[i].nodeID == -1 || !entryArray[i].memBit)
			continue;

		if(minEntry == -1)
		{
			minEntry = i;
			earlyTime = entryArray[i].last_time;
		}
		else if(entryArray[i].last_time < earlyTime)
		{
			minEntry = i;
			earlyTime = entryArray[i].last_time;
		}

		/*
		if(!entryArray[i].dirtyBit)
		{
			if(minCleanEntry == -1)
			{
				minCleanEntry = i;
				earlyCleanTime = entryArray[i].last_time;
			}
			else if(entryArray[i].last_time < earlyCleanTime)
			{
				earlyCleanTime = entryArray[i].last_time;
			}
		}
		*/
	}

	//if(minCleanEntry != -1)
	//	return minCleanEntry;

	return minEntry;
}


int BufferManager::RequestID()
{
	if(curSize == maxSize)
	{
		cout << "new page number : " << maxSize * 2 << endl;

		int newMaxSize = maxSize * 2;

		BufTabEntry* newEntryArray = new BufTabEntry[newMaxSize];

		int i;

		for(i = 0; i < maxSize; i++)
		{
			newEntryArray[i].pointer = entryArray[i].pointer;
			newEntryArray[i].last_time = entryArray[i].last_time;
			newEntryArray[i].memBit = entryArray[i].memBit;
			newEntryArray[i].nodeID = entryArray[i].nodeID;
			newEntryArray[i].dirtyBit = entryArray[i].dirtyBit;
		}

		for(i = maxSize; i < newMaxSize; i++)
			newEntryArray[i].nodeID = -1;

		delete [] entryArray;
		entryArray = newEntryArray;
		maxSize = newMaxSize;
		}

	curSize++;

	return curSize - 1;
}

bool BufferManager::isDirty(int id)
{
	if(id < 0 || id >= maxSize)
	{
		cout << "wrong id " << endl;
		return false;
	}

	return entryArray[id].dirtyBit;
}

/*
void BufferManager::CheckConsistency()
{
	int i;

	for(i = 0; i < maxSize; i++)
	{
		if(entryArray[i].nodeID != -1)
		{
			if(((Page*)(entryArray[i].pointer))->isInMem && (!entryArray[i].memBit))
			{
				cout << "error" << endl;
			}
			if(! ((Page*)(entryArray[i].pointer))->isInMem && (entryArray[i].memBit))
			{
				cout << "error" << endl;
			}
		}
	}
}
*/


